
package com.studiofortress.sf.graphics;

import com.studiofortress.sf.util.Resource;
import com.sun.opengl.util.texture.Texture;
import com.sun.opengl.util.texture.TextureCoords;
import com.sun.opengl.util.texture.TextureIO;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.media.opengl.GLException;

/**
 * This is the real wrapper for the Texture class. This adds some abstraction on
 * top such as queueing to be loaded at a later date and pre-calculating some
 * required values.
 * 
 * @author Joseph Lenton
 */
class GLTextureInner
{
    private final String path;
    private Texture texture;
    private int textNum;

    // this is the real width/height of the image used, in pixels
    private int initialWidth;
    private int initialHeight;

    private boolean isLoaded;
    private BufferedImage image;

    private float left;
    private float right;
    private float bottom;
    private float top;

    public GLTextureInner(String name, BufferedImage image)
    {
        this.path = name;
        this.image = image;
        this.initialWidth = image.getWidth();
        this.initialHeight = image.getHeight();
        this.isLoaded = false;
        
        GLTexture.getTextureManager().addInitializeTexture( this );
    }

    /**
     * Creates a new texture from the given path.
     *
     * Note that this is slower then the alternate constructor where the width
     * and height is passed in. If possible that one should be used instead.
     * @param path The path to the texture to load. Cannot be null.
     */
    public GLTextureInner(String path)
    {
        try {
            if (path == null) {
                throw new IllegalArgumentException("The given path cannot be null.");
            }

            InputStream input = Resource.getStream(path);
            if (input == null) {
                throw new IllegalArgumentException("The given path was not found: " + path);
            }

            image = ImageIO.read( new BufferedInputStream(input) );
            if (image != null) {
                initialWidth = image.getWidth();
                initialHeight = image.getHeight();
            }

            this.path = path;
            this.isLoaded = false;
            GLTexture.getTextureManager().addInitializeTexture( this );
        } catch (FileNotFoundException e) {
            throw new IllegalArgumentException("The given path was not found: " + path);
        } catch (IOException e) {
            throw new IllegalArgumentException("Error reading texture: " + path);
        }
    }

    /**
     * Loads the texture and associates it with the current GL pipeline.
     * This is done here rather then in the constructor to allow textures to be
     * conceptually loaded before the GL pipeline exists.
     */
    final void initialize()
    {
        if (!isLoaded()) {
            try {
                if (image == null) {
                    final int lastDot = path.lastIndexOf(".");
                    final String extension = path.substring(lastDot, path.length());
                    this.texture = TextureIO.newTexture(
                            new BufferedInputStream(Resource.getStream(path)),
                            false,
                            extension );

                    TextureCoords coords = this.texture.getImageTexCoords();
                    this.initialWidth  = Math.round(texture.getWidth() * (coords.right() - coords.left()));
                    this.initialHeight = Math.round(texture.getHeight() * (coords.bottom() - coords.top()));
                } else {
                    this.texture = TextureIO.newTexture(image, false);
                    image = null;
                }

                this.textNum = texture.getTarget();

                if (initialWidth <= 0 || initialWidth > texture.getWidth()) {
                    throw new IllegalArgumentException(
                            "The true width of the texture must be greater then 0 and less then or equal to the power of 2 width of the texture: " + initialWidth);
                }
                if (initialHeight <= 0 || initialHeight > texture.getHeight()) {
                    throw new IllegalArgumentException(
                            "The true height of the texture must be greater then 0 and less then or equal to the power of 2 height of the texture: " + initialHeight);
                }

                TextureCoords coords = this.texture.getImageTexCoords();
                this.top = coords.top();
                this.bottom = coords.bottom();
                this.left = coords.left();
                this.right = coords.right();
                
                isLoaded = true;
            } catch (GLException ex) {
                throw new RuntimeException("OpenGL Error reading image: " + path, ex);
            } catch (IOException ex) {
                throw new RuntimeException("OpenGL Error reading image: " + path, ex);
            }
        }
    }

    final float getBottomCoord()
    {
        return bottom;
    }

    final float getTopCoord()
    {
        return top;
    }

    final float getLeftCoord()
    {
        return left;
    }

    final float getRightCoord()
    {
        return right;
    }

    /**
     * @return True if this texture has been loaded and initialised.
     */
    boolean isLoaded()
    {
        return isLoaded;
    }

    /**
     * Disposes the texture that is used internally.
     * @throws java.lang.Throwable
     */
    @Override
    protected void finalize()
            throws Throwable
    {
        super.finalize();
        texture.dispose();
    }

    /**
     * @return The intrnal OpenGL texture object that this GLTexture wraps.
     */
    public final Texture getTexture()
    {
        return texture;
    }

    /**
     * @return The value that represents this texture on the graphics card.
     */
    public final int getTextNum()
    {
        return textNum;
    }

    /**
     * @return The true width of the texture.
     */
    public final int getWidth()
    {
        return initialWidth;
    }

    /**
     * @return The true height of the texture.
     */
    public final int getHeight()
    {
        return initialHeight;
    }

    /**
     * Internally binds the texture to the corresponding OpenGL pipeline.
     */
    final void bind()
    {
        texture.bind();
    }

    /**
     * Internally enables this texture for use with the current OpenGL pipeline.
     */
    final void enable()
    {
        texture.enable();
    }

    final void disable()
    {
        texture.disable();
    }

    /**
     * @param x The x position of the colour to return.
     * @param y The y position of the colour to return.
     * @return The colour at this pixel as an integer in the format of an INT-ARGB
     * @throws IllegalArgumentException If the x or y co-ordinate is outside of the texture.
     */
    public int getRGB(int x, int y)
    {
        if (x < 0 || y < 0 || x >= getWidth() || y >= getHeight()) {
            throw new IllegalArgumentException("X and Y must be within the bounds of the textures image, x: " + x + ", y: " + y);
        }
        
        throw new UnsupportedOperationException("getRGB has not yet been implemented.");
    }
}
